import { ESJInstances } from "earthsdk3";
import { positionToCartesian } from "../../../../utils";
import * as Cesium from "cesium";
const positionScratch = new Cesium.Cartesian3();
const hprScratch = new Cesium.HeadingPitchRoll();
const hprMatrix3Scratch = new Cesium.Matrix3();
const hprMatrix3QScratch = new Cesium.Quaternion();
//@ts-ignore
const Attribute = Cesium.ModelComponents.Attribute;
//@ts-ignore
const FeatureIdAttribute = Cesium.ModelComponents.FeatureIdAttribute;
//@ts-ignore
const Instances = Cesium.ModelComponents.Instances;
export function createInstances(loader: any, components: any, frameState: Cesium.FrameState, loadInstances: ESJInstances | undefined) {
    if (!loadInstances) return;
    let i;
    const instancesLength = loadInstances ? loadInstances.length : 0;
    if (instancesLength === 0) return;
    //初始化
    const translationTypedArray = new Float32Array(3 * instancesLength);
    const rotationTypedArray = new Float32Array(4 * instancesLength);
    const scaleTypedArray = new Float32Array(3 * instancesLength);
    const featureIdArray = new Float32Array(instancesLength);

    const instanceQuaternionArray = new Array(4);
    const instanceScaleArray = new Array(3);
    // 通过instances取得中心点计算局部坐标，可以跟随整体modelMatrix进行移动    
    const positionBoundingSphere = Cesium.BoundingSphere.fromPoints(loadInstances.map((item: any) => { return positionToCartesian(item.position) }));
    const inverseModelMatrix = Cesium.Matrix4.inverse(Cesium.Transforms.northWestUpToFixedFrame(positionBoundingSphere.center), new Cesium.Matrix4());
    for (i = 0; i < instancesLength; i++) {
        // 填充位置数组
        Cesium.Matrix4.multiplyByPoint(inverseModelMatrix, positionToCartesian(loadInstances[i].position), positionScratch)
        // Cesium.Cartesian3.subtract(instancePositions[i], positionBoundingSphere.center, positionScratch);
        translationTypedArray[3 * i + 0] = positionScratch.x;
        translationTypedArray[3 * i + 1] = positionScratch.y;
        translationTypedArray[3 * i + 2] = positionScratch.z;
        // 填充旋转数组
        // const temp = Cesium.Matrix4.multiplyByMatrix3(inverseModelMatrix, instanceRotations[i], new Cesium.Matrix4())
        // Cesium.Quaternion.pack(Cesium.Quaternion.fromRotationMatrix(Cesium.Matrix4.getRotation(temp, new Cesium.Matrix3())), instanceQuaternionArray, 0);
        Cesium.Matrix3.fromHeadingPitchRoll(Cesium.HeadingPitchRoll.fromDegrees(...((loadInstances[i].rotation as [number, number, number]) ?? [0, 0, 0]), hprScratch), hprMatrix3Scratch)
        Cesium.Quaternion.pack(Cesium.Quaternion.fromRotationMatrix(hprMatrix3Scratch, hprMatrix3QScratch), instanceQuaternionArray, 0);
        rotationTypedArray[4 * i + 0] = instanceQuaternionArray[0];
        rotationTypedArray[4 * i + 1] = instanceQuaternionArray[1];
        rotationTypedArray[4 * i + 2] = instanceQuaternionArray[2];
        rotationTypedArray[4 * i + 3] = instanceQuaternionArray[3];
        // 填充缩放数组
        Cesium.Cartesian3.pack(Cesium.Cartesian3.fromArray(loadInstances[i].scale ?? [1, 1, 1]), instanceScaleArray, 0);
        scaleTypedArray[3 * i + 0] = instanceScaleArray[1];
        scaleTypedArray[3 * i + 1] = instanceScaleArray[0];
        scaleTypedArray[3 * i + 2] = instanceScaleArray[2];
        // 填充featureId数组
        featureIdArray[i] = i;
    }
    // Create instances.
    const instances = new Instances();
    instances.transformInWorldSpace = true;
    if (!loader._buffers) loader._buffers = [];
    const buffers = loader._buffers;

    // Create translation vertex attribute.
    const translationAttribute = new Attribute();
    translationAttribute.name = "Instance Translation";
    //@ts-ignore
    translationAttribute.semantic = Cesium.InstanceAttributeSemantic.TRANSLATION;
    translationAttribute.componentDatatype = Cesium.ComponentDatatype.FLOAT;
    //@ts-ignore
    translationAttribute.type = Cesium.AttributeType.VEC3;
    translationAttribute.count = instancesLength;
    // The min / max values of the translation attribute need to be computed
    // by the model pipeline, so so a pointer to the typed array is stored.
    translationAttribute.typedArray = translationTypedArray;
    // If there is no rotation attribute, however, the translations can also be
    // loaded as a buffer to prevent additional resource creation in the pipeline.

    instances.attributes.push(translationAttribute);

    // Create rotation vertex attribute.
    const rotationAttribute = new Attribute();
    rotationAttribute.name = "Instance Rotation";
    //@ts-ignore
    rotationAttribute.semantic = Cesium.InstanceAttributeSemantic.ROTATION;
    rotationAttribute.componentDatatype = Cesium.ComponentDatatype.FLOAT;
    //@ts-ignore
    rotationAttribute.type = Cesium.AttributeType.VEC4;
    rotationAttribute.count = instancesLength;
    rotationAttribute.typedArray = rotationTypedArray;
    instances.attributes.push(rotationAttribute);

    // Create scale vertex attribute.
    const scaleAttribute = new Attribute();
    scaleAttribute.name = "Instance Scale";
    //@ts-ignore
    scaleAttribute.semantic = Cesium.InstanceAttributeSemantic.SCALE;
    scaleAttribute.componentDatatype = Cesium.ComponentDatatype.FLOAT;
    //@ts-ignore
    scaleAttribute.type = Cesium.AttributeType.VEC3;
    scaleAttribute.count = instancesLength;
    // If rotations are present, all transform attributes are loaded
    // as typed arrays to compute transform matrices for the model.
    scaleAttribute.typedArray = scaleTypedArray;

    instances.attributes.push(scaleAttribute);

    // Create feature ID vertex attribute.
    const featureIdAttribute = new Attribute();
    featureIdAttribute.name = "Instance Feature ID";
    featureIdAttribute.setIndex = 0;
    //@ts-ignore
    featureIdAttribute.semantic = Cesium.InstanceAttributeSemantic.FEATURE_ID;
    featureIdAttribute.componentDatatype = Cesium.ComponentDatatype.FLOAT;
    //@ts-ignore
    featureIdAttribute.type = Cesium.AttributeType.SCALAR;
    featureIdAttribute.count = instancesLength;
    const buffer = Cesium.Buffer.createVertexBuffer({
        context: frameState.context,
        typedArray: featureIdArray,
        usage: Cesium.BufferUsage.STATIC_DRAW,
    });
    // Destruction of resources is handled by I3dmLoader.unload().
    //@ts-ignore
    buffer.vertexArrayDestroyable = false;
    buffers.push(buffer);
    featureIdAttribute.buffer = buffer;

    instances.attributes.push(featureIdAttribute);

    // Create feature ID attribute.
    const featureIdInstanceAttribute = new FeatureIdAttribute();
    featureIdInstanceAttribute.propertyTableId = 0;
    featureIdInstanceAttribute.setIndex = 0;
    featureIdInstanceAttribute.positionalLabel = "instanceFeatureId_0";
    instances.featureIds.push(featureIdInstanceAttribute);

    // Apply instancing to every node that has at least one primitive.
    const nodes = components.nodes;
    const nodesLength = nodes.length;
    let makeInstancesCopy = false;
    for (i = 0; i < nodesLength; i++) {
        const node = nodes[i];
        if (node.primitives.length > 0) {
            // If the instances have not been assigned to a node already, assign
            // it to the first node encountered. Otherwise, make a copy of them
            // for each subsequent node.
            node.instances = makeInstancesCopy
                ? createInstancesCopy(instances)
                : instances;
            makeInstancesCopy = true;
        }
    }
}

export function createStructuralMetadata(loadInstances: ESJInstances | undefined, components: any) {
    const instancesLength = loadInstances ? loadInstances.length : 0;

    if (instancesLength === 0) return;

    let structuralMetadata;
    if (loadInstances?.[0] && Reflect.has(loadInstances[0], "id")) {
        const properties = {
            id: new Array(instancesLength),
        }
        for (let i = 0; i < instancesLength; i++) {
            properties.id[i] = loadInstances[i].id ?? i;
        }
        //@ts-ignore
        const jsonMetadataTable = new Cesium.JsonMetadataTable({
            count: instancesLength,
            properties: properties,
        })
        //@ts-ignore
        const propertyTable = new Cesium.PropertyTable({
            //@ts-ignore
            name: Cesium.MetadataClass.BATCH_TABLE_CLASS_NAME,
            count: instancesLength,
            jsonMetadataTable
        });
        //@ts-ignore
        structuralMetadata = new Cesium.StructuralMetadata({
            schema: {},
            propertyTables: [propertyTable],
        });
    } else {
        // If batch table is not defined, create a property table without any properties.
        //@ts-ignore
        const emptyPropertyTable = new Cesium.PropertyTable({
            //@ts-ignore
            name: Cesium.MetadataClass.BATCH_TABLE_CLASS_NAME,
            count: instancesLength,
        });
        //@ts-ignore
        structuralMetadata = new Cesium.StructuralMetadata({
            schema: {},
            propertyTables: [emptyPropertyTable],
        });
    }
    components.structuralMetadata = structuralMetadata;
}

/**
 * Returns a copy of the instances that contains shallow copies of the instanced
 * attributes. That is, the instances and attribute objects will be new copies,
 * but they will point to the same buffers and typed arrays. This is so each
 * node can manage memory separately, such that unloading memory for one
 * node does not unload it for another.
 *
 * @returns {ModelComponents.Instances}
 *
 * @private
 */
//@ts-ignore
function createInstancesCopy(instances: Cesium.ModelComponents.Instances): Cesium.ModelComponents.Instances {
    //@ts-ignore
    const instancesCopy = new Instances();
    instancesCopy.transformInWorldSpace = instances.transformInWorldSpace;

    const attributes = instances.attributes;
    const attributesLength = attributes.length;

    for (let i = 0; i < attributesLength; i++) {
        const attributeCopy = Cesium.clone(attributes[i], false);
        instancesCopy.attributes.push(attributeCopy);
    }

    // instancesCopy.featureIds = instances.featureIds;

    return instancesCopy;
}